/*
 *  Ontology.c
 *  IncrementalAlgorithmSerial
 *
 *  Created by Roman Kutlak on 27/03/2010.
 *  Copyright 2010 Roman Kutlak. All rights reserved.
 *
 */

#include <fstream>
#include <iterator>

#include "Ontology.h"
#include "Voc.h"

using namespace std;

namespace kutlak {
namespace roman {
namespace gre {
    
    
    std::once_flag Ontology::sFlag;
    std::vector<Property> Ontology::sNamedEntityTypes;
    
    /*
     //    const string Ontology::kType = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
     //    
     //    const string Ontology::kThing = "http://www.w3.org/2002/07/owl#Thing";
     //    
     //    const string Ontology::kPerson = "http://dbpedia.org/ontology/Person";
     //    
     //    const string Ontology::kSubClassOf = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
     //    
     //    const string Ontology::kEquivalentClass = "http://www.w3.org/2002/07/owl#equivalentClass";
     //    
     //    const string Ontology::kObjectProperty = "http://www.w3.org/2002/07/owl#ObjectProperty";
     //    
     //    const string Ontology::kEquivalentProperty = "http://www.w3.org/2002/07/owl#equivalentProperty";
     */
    
    Voc& v = Voc::Instance();
    
    // important attributes
    const Vindex_t Ontology::kType = 
    v.Index("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
    
    const Vindex_t Ontology::kSubClassOf = 
    v.Index("http://www.w3.org/2000/01/rdf-schema#subClassOf");
    
    const Vindex_t Ontology::kEquivalentClass = 
    v.Index("http://www.w3.org/2002/07/owl#equivalentClass");
    
    const Vindex_t Ontology::kRange = 
    v.Index("http://www.w3.org/2000/01/rdf-schema#range");
    
    // important values
    const Vindex_t Ontology::kThing = 
    v.Index("http://www.w3.org/2002/07/owl#Thing");
    
    const Vindex_t Ontology::kPerson = 
    v.Index("http://dbpedia.org/ontology/Person");
    
    // important properties
    const Vindex_t Ontology::kObjectProperty = 
    v.Index("http://www.w3.org/2002/07/owl#ObjectProperty");
    
    const Vindex_t Ontology::kEquivalentProperty = 
    v.Index("http://www.w3.org/2002/07/owl#equivalentProperty");
    
    // useful properties
    const Property Ontology::kThingType = Property(kType, kThing);
    const Property Ontology::kPersonType = Property(kType, kPerson);
    
    
#pragma mark -
#pragma mark lifecycle
    
    // set default ontology to null.
    pOntology Ontology::sDefault = pOntology();
    
    Ontology::Ontology(const string& fileName) :
    mStatements(), mSubsumptionCache(10000) 
    {
        std::call_once(sFlag, PopulateNamedEntityList);
        
        if ("" != fileName)
            OpenFile(fileName);
    }
    
    Ontology::Ontology(const Ontology& from) :
    mStatements(from.mStatements),
    mSubsumptionCache(from.mSubsumptionCache) {}
    
    
    Ontology::Ontology(Ontology&& from) :
    mStatements(std::move(from.mStatements)),
    mSubsumptionCache(std::move(from.mSubsumptionCache)) {}
    
    Ontology& Ontology::operator=(Ontology rhs) 
    {
        swap(*this, rhs);
        return *this;
    }
    
    Ontology::~Ontology() noexcept(true) = default;
    
    void swap(Ontology& left, Ontology& right)
    {
        using std::swap;
        swap(left.mStatements, right.mStatements);
        swap(left.mSubsumptionCache, right.mSubsumptionCache);
    }
    
    void Ontology::SetDefault(const pOntology& ontology) 
    {
        sDefault = ontology;
    }
    
    pOntology Ontology::Default() 
    {
        return sDefault;
    }
    
    void Ontology::AddStatement(const Statement& statement) 
    {
        mStatements.push_back(statement);
    }
    
    void Ontology::AddStatement(Statement&& statement) 
    {
        mStatements.push_back(std::move(statement));
    }
    
#pragma mark -
#pragma mark ontology methods
    
    std::vector<Property> Ontology::MoreSpecificValues(const Property& property) const 
    {
        std::vector<Property> result;
        
        // more specific property is a subclass of the current property
        Ontology::ContainerT tmp = Search(DEFAULT_ATTRIBUTE,
                                          Ontology::kSubClassOf,
                                          property.Value());
        
        for_each (tmp.begin(), tmp.end(), [&result,&property](Statement& s) 
                  {
                      result.push_back(Property(property.Attribute(), s.mSubject));
                  });
        
        return result;
    }
    
    Property Ontology::BasicLevelValue(const Property& property) const 
    {
        std::vector<Property> result;
        Property p(property.Attribute(), Ontology::kThing);
        // what ever is a subclass of owl:thing is the most basic value
        result = MoreSpecificValues(p); // this does not work well because it will return species before plant...
        // it would be better if we could use some corpus of simple english to look for a property.
        for (const auto& prop : result) 
        {
            if (Subsumes(prop, property))
                return prop;
        }
        
        return property;
    }
    
    std::vector<Property> Ontology::LessSpecificValues(const Property& property) const 
    {
        std::vector<Property> result;
        
        // less specific property is a superclass of the current property
        Ontology::ContainerT tmp = Search(property.Value(), Ontology::kSubClassOf, DEFAULT_VALUE);
        
        for_each(tmp.begin(), tmp.end(), [&result, &property](Statement& s) 
                 {
                     result.push_back(Property(property.Attribute(), s.mObject));
                 });
        
        return result;
    }
    
    Property Ontology::LessSpecificValue(const Property& property) const 
    {
        std::vector<Property> result = LessSpecificValues(property);
        
        if (result.empty())
            return property;

        // return one of the values
        return *result.begin();
    }
    
    bool Ontology::Subsumes(const std::string& parent, const std::string& child) const 
    {
        Voc& v = Voc::Instance();
        Property p1(kType, v.Index(parent));
        Property p2(kType, v.Index(child));
        
        return Subsumes(p1, p2);
    }
    
    bool Ontology::Subsumes(const Property& parent, const Property& child) const 
    {
        // the default property does not subsume anything
        if (parent.Value() == DEFAULT_VALUE ||
            parent.Attribute() == DEFAULT_ATTRIBUTE ||
            child.Value() == DEFAULT_VALUE ||
            child.Attribute() == DEFAULT_ATTRIBUTE)
            return false;
        
        // owl:Thing subsumes everything
        if (Ontology::kThingType == parent)
            return true;
        
        // nothing subsumes owl:Thing
        if (Ontology::kThingType == child)
            return false;
        
        bool result = false;
        if (mSubsumptionCache.Find(KeyT(parent, child), result))
            return result;
        
        result = CheckSubsumes(parent, child);

        mSubsumptionCache.Insert(KeyT(parent, child), std::move(result));
        
        return result;
    }
    
    // exhaustive BFS
    bool Ontology::CheckSubsumes(const Property& parent, const Property& child) const 
    {   
        std::vector<Property> ancestors;
        ancestors.push_back(child);
        
        while (!ancestors.empty())
        {
            if (IsEquivalentClass(ancestors.back().Value(), parent.Value()))
                return true;
        
            // traverse the ontology down one level
            std::vector<Property> tmp = LessSpecificValues(ancestors.back());
            ancestors.pop_back();
            
            std::move(tmp.begin(), tmp.end(), std::back_inserter(ancestors));
        }
        
        return false;
    }
    
    void Ontology::Print(int i) const 
    {
        int progress = 0;
        
        for (const auto& statement : mStatements) 
        {
            cout << statement << endl;
            
            // if there is a limit on how many statements to print,
            // stop when the limit is reached
            if (-1 != i && i == ++progress)
                break;
        }
    }
    
    void Ontology::Clear() 
    {
        mStatements.clear();
        mSubsumptionCache.Clear();
    }
    
    // TODO: possibly add cache for this method
    bool Ontology::IsEquivalentClass(const Vindex_t& class1,
                                     const Vindex_t& class2) const 
    {
        // not frequent but could speed things up
        if (class1 == class2)
            return true;
        
        vector<Vindex_t> candidates;
        vector<Vindex_t> done;
        // start with
        candidates.push_back(class1);
        
        while (!candidates.empty()) 
        {
            Vindex_t ci = candidates.front();
            candidates.erase(candidates.cbegin());
            
            // have we done this one?
            if (done.end() != (find(done.begin(), done.end(), ci)))
                continue;
            
            // if class2 is in the list, it is equivalent class of class1
            if (ci == class2)
                return true;
            
            // mark the current candidate as done
            done.push_back(ci);
            // add all children of ci to candidate list
            vector<Vindex_t> tmp = EquivalentClasses(ci);
            std::copy(tmp.begin(), tmp.end(), std::back_inserter(candidates));
        }
        
        return false;
    }
    
    vector<Vindex_t> Ontology::EquivalentClasses(const Vindex_t& c) const 
    {
        vector<Vindex_t> result;
        for (const Statement& s : mStatements) 
        {
            if (s.mSubject == c && s.mPredicate == kEquivalentClass)
                result.push_back(s.mObject);
            
            if (s.mObject == c && s.mPredicate == kEquivalentClass)
                result.push_back(s.mSubject);
        }
        return result;
    }
    
    bool Ontology::IsNamedEntity(const EntityPtrT& entity) const 
    {
        for (const auto& neType : sNamedEntityTypes) 
        {
            if (entity->HasProperty(neType)) // i.e. entity->IsType(*it)
                return true;
        }
        
        return false;
    }
    
    bool Ontology::RangeIsNamedEntity(const Vindex_t& val) const 
    {
        if (DEFAULT_VALUE == val)
            return false;
        
        ContainerT tmp = Search(val, kRange, DEFAULT_ATTRIBUTE);
        
        for (const auto& attrRange : tmp) 
        {
            for (const auto& neType : sNamedEntityTypes) 
            {
                if (attrRange.mObject == neType.Value()) // one of the domains is a type of NE
                    return true;
            }
        }
        return false;
    }
    
    
    bool Ontology::IsRelation(const Property& p) const 
    {
        return IsRelation(p.Attribute());
    }
    
    bool Ontology::IsRelation(const Vindex_t& attr) const 
    {
        for (const auto& s : mStatements) 
        {
            if (s.mSubject == attr &&
                s.mPredicate == kType &&
                s.mObject == kObjectProperty)
                return true;
        }
        
        return false;
    }
    
    
#pragma mark -
#pragma mark protected methods
    
    Ontology::ContainerT Ontology::Search(const string& subject, 
                                          const string& predicate,
                                          const string& object) const 
    {
        
        Voc& v = Voc::Instance();
        return Search(v.Index(subject), 
                      v.Index(predicate),
                      v.Index(object));
    }
    
    Ontology::ContainerT Ontology::Search(const string& subject, 
                                          const Vindex_t& predicate,
                                          const string& object) const 
    {
        
        Voc& v = Voc::Instance();
        return Search(v.Index(subject), 
                      predicate,
                      v.Index(object));
    }
    
    Ontology::ContainerT Ontology::Search(const Vindex_t& subject,
                                          const Vindex_t& predicate,
                                          const Vindex_t& object) const 
    {
        
        Ontology::ContainerT results;
        
        Ontology::ContainerT::const_iterator it, end;
        for (it = mStatements.begin(), end = mStatements.end(); 
             it != end; ++it) 
        {
            
            if (subject != DEFAULT_ATTRIBUTE && subject == it->mSubject) 
            {
                if (predicate == DEFAULT_ATTRIBUTE || predicate == it->mPredicate) 
                {
                    if (object == DEFAULT_ATTRIBUTE || object == it->mObject) 
                    {
                        results.push_back(*it);
                    }
                }
            }
            if (subject == DEFAULT_ATTRIBUTE) 
            {
                if (predicate == DEFAULT_ATTRIBUTE || predicate == it->mPredicate) 
                {
                    if (object == DEFAULT_ATTRIBUTE || object == it->mObject) 
                    {
                        results.push_back(*it);
                    }
                }
            }
        }
        
        return results;
    }
    
    size_t Ontology::Size() const 
    {
        return mStatements.size();
    }
    
    void Ontology::PopulateNamedEntityList() 
    {
        Voc& v = Voc::Instance();
        
        sNamedEntityTypes.push_back(Ontology::kPersonType);
        sNamedEntityTypes.push_back
        (Property(kType, 
                  v.Index("http://dbpedia.org/ontology/Place")));
        sNamedEntityTypes.push_back
        (Property(kType,
                  v.Index("http://dbpedia.org/ontology/Company")));
        sNamedEntityTypes.push_back
        (Property(kType,
                  v.Index("http://dbpedia.org/ontology/Organisation")));
        sNamedEntityTypes.push_back
        (Property(kType,
                  v.Index("http://dbpedia.org/ontology/EducationalInstitution")));
        sNamedEntityTypes.push_back
        (Property(kType,
                  v.Index("http://dbpedia.org/ontology/Country")));
        sNamedEntityTypes.push_back
        (Property(kType,
                  v.Index("http://dbpedia.org/ontology/Band")));
        sNamedEntityTypes.push_back
        (Property(kType,
                  v.Index("http://dbpedia.org/ontology/University")));
        sNamedEntityTypes.push_back
        (Property(kType,
                  v.Index("http://dbpedia.org/ontology/FictionalCharacter")));
        sNamedEntityTypes.push_back
        (Property(kType,
                  v.Index("http://dbpedia.org/ontology/Work")));
        
    }
    
    
#pragma mark -
#pragma mark I/O
    
    ostream& operator<<(ostream& os, const Ontology& obj) 
    {
        obj.Serialise(os);
        return os;
    }
    
    istream& operator>>(istream& is, Ontology& obj) 
    {
        Ontology tmp;
        tmp.Deserialise(is);
        swap(tmp, obj);
        return is;
    }
    
    void Ontology::Serialise(ostream& os) const 
    {
        os << "<ontology>\n";
        for (const auto& s : mStatements) 
        {
            os << s;
        }
        os << "</ontology>\n";
    }
    
    void Ontology::Deserialise(istream& is) 
    {
        string tmp;
        if (!(is >> tmp) || "<ontology>" != tmp) 
        {
            string message = "Expected <ontology>.";
            throw DeserialisationException(std::move(message),
                                           std::move(tmp));
        }
        
        try 
        {
            Statement s;
            while (is >> s)
                mStatements.push_back(s);
        } 
        catch (DeserialisationException& de) 
        {
            if ("</ontology>" != de.data()) 
            {
                string message = "Exception while deserialising Ontology: ";
                throw DeserialisationException(message + de.what(),
                                               std::move(de.data()));
            }
        }
    }
    
    void Ontology::OpenFile(std::string const &fileName) 
    {
        if ("" == fileName)
            return;
        
#ifdef ONTOLOGY_DEBUG
        cerr << "Calling Ontology::OpenFile(" << fileName << ")." << endl;
#endif
        
        ifstream input;
        input.open(fileName.c_str());
        
        if (!input.is_open()) 
        {
            cerr << __FILE__ << " (" << __LINE__ << "): ";
            cerr << "Could not open the input file ";
            cerr << fileName << endl;
            return;
        }
        
        FromRdf(input);
        input.close();
    }
    
    istream& Ontology::FromRdf(istream& in) 
    {
#ifdef ONTOLOGY_DEBUG
        cerr << "Calling Ontology::OpenFile(istream)." << endl;
#endif
        
        Voc& v = Voc::Instance();
        
        string subject;
        string predicate;
        string object;
        string end;
        
        while(in >> subject >> predicate && getline(in, object)) 
        {
            
            if (subject.front() == '#') // commented line
                continue;
            
            // remove '<' and '>' if found
            {
                
                if (subject.front() == '<')
                    subject = subject.substr(1);
                if (subject.back() == '>')
                    subject.pop_back();
                
                if (predicate.front() == '<')
                    predicate = predicate.substr(1);
                if (predicate.back() == '>')
                    predicate.pop_back();
                
                // remove the trailing " ."
                size_t pos = object.find_last_of(" ");    
                if (string::npos != pos)
                    object = object.substr(0, pos);
                
                // remove spaces befor and after the string 
                object = Trim(object, false);
                
                if (object.front() == '<')
                    object = object.substr(1);
                if (object.back() == '>')
                    object.pop_back();
            }
            
            AddStatement(Statement(v.Index(subject),
                                   v.Index(predicate),
                                   v.Index(object)));
        }
        
        return in;
    }
    
    void Ontology::SaveFile(const string& fileName) const 
    {
        ofstream output;
        output.open(fileName.c_str());
        
        if (!output.is_open()) 
        {
            cerr << __FILE__ << " (" << __LINE__ << "): ";
            cerr << "Could not open the output file ";
            cerr << fileName << endl;
            return;
        }
        
        ToRdf(output);
        output.close();
    }
    
    ostream& Ontology::ToRdf(ostream& out) const 
    {
        for (const auto& s : mStatements) 
            out << s.ToRdf();
        
        return out;
    }
    
} // end namespace gre
} // end namespace roman
} // end namespace kutlak
